﻿@using System.Collections.Concurrent
@using Action = Opserver.Data.HAProxy.Action
@model HAProxyModel
@{
    Layout = "HAProxy.cshtml";

    var isAdmin = Current.User.Is(HAProxyRoles.Admin);
    // TODO: Fold in
    var groupQS = Model.SelectedGroup != null ? "&group=" + Model.SelectedGroup.Name.UrlEncode() : "";
    var proxies = Model.Proxies;
    var anyAdmin = isAdmin && proxies.Any(p => p.Servers.Any());
    // move critical stuff to the top
    // TODO: Decide on admin sort
    var groups = proxies.GroupBy(p => new
    {
        p.Name,
        p.GroupLinkName,
        GroupName = p.Instance.Group.Name,
        ProxyName = p.Name,
        p.NiceName,
        p.Instance.HasAdminLogin
    }).Select(g => new ProxyGroupKey(g.Key.Name,
        g.Key.GroupLinkName,
        g.Key.GroupName,
        g.Key.ProxyName,
        g.Key.NiceName,
        g.Key.HasAdminLogin,
        g.GetWorstStatus(),
        g.SelectMany(p => p.Servers),
        g.SelectMany(p => p.AllStats),
        g.Max(p => p.PollDate)))
        .OrderByDescending(g => g.GroupLinkName == Model.WatchProxy)
        .ThenBy(g => g.GroupStatus == MonitorStatus.Good)
        .ThenBy(g => g.AllServers.GetWorstStatus() == MonitorStatus.Warning)
        .ToList();

    foreach (var p in proxies)
    {
        foreach (var s in p.AllStats)
        {
            statMapping.Add(s, p);
        }
    }
    var instances = Model.Groups.SelectMany(g => g.Instances).ToList();
    var erroring = instances.Where(i => i.Proxies.ErrorMessage.HasValue()).ToList();
}
@functions
{
    readonly Dictionary<Item, Proxy> statMapping = new Dictionary<Item, Proxy>();
    string CombinedStat(string title, IEnumerable<Item> stats, Func<Item, string> getter)
    {
        var sb = StringBuilderCache.Get()
                .Append(title)
                .Append(':')
                .AppendLine();
        foreach (var s in stats)
        {
            sb.Append(statMapping[s].Instance.Name)
              .Append(": ")
              .Append(getter(s))
              .Append('\n');
        }
        sb.Length -= 1;
        return sb.ToStringRecycle();
    }

    string GetRowClass(StatusType type)
    {
        switch (type)
        {
            case StatusType.Frontend:
            case StatusType.Backend:
                return "active";
        }
        return null;
    }

    public class ProxyGroupKey : IEquatable<ProxyGroupKey>
    {
        public string Name { get; }
        public string GroupLinkName { get; }
        public string GroupName { get; }
        public string ProxyName { get; }
        public string NiceName { get; }
        public bool HasAdminLogin { get; }
        public MonitorStatus GroupStatus { get; }
        public IEnumerable<Server> AllServers { get; }
        public IEnumerable<Item> AllStats { get; }
        public DateTime PollDate { get; }

        public ProxyGroupKey(string name, string groupLinkName, string groupName, string proxyName, string niceName, bool hasAdminLogin, MonitorStatus groupStatus, IEnumerable<Server> servers, IEnumerable<Item> stats, DateTime pollDate)
        {
            Name = name;
            GroupLinkName = groupLinkName;
            GroupName = groupName;
            ProxyName = proxyName;
            NiceName = niceName;
            HasAdminLogin = hasAdminLogin;
            GroupStatus = groupStatus;
            AllServers = servers;
            AllStats = stats;
            PollDate = pollDate;
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
            {
                return false;
            }
            if (ReferenceEquals(this, obj))
            {
                return true;
            }
            if (obj.GetType() != this.GetType())
            {
                return false;
            }
            return Equals((ProxyGroupKey)obj);
        }

        public bool Equals(ProxyGroupKey other)
        {
            if (ReferenceEquals(null, other))
            {
                return false;
            }
            if (ReferenceEquals(this, other))
            {
                return true;
            }
            return string.Equals(Name, other.Name) && string.Equals(GroupLinkName, other.GroupLinkName) && string.Equals(GroupName, other.GroupName) && string.Equals(ProxyName, other.ProxyName) && string.Equals(NiceName, other.NiceName) && HasAdminLogin == other.HasAdminLogin && GroupStatus == other.GroupStatus && Equals(AllServers, other.AllServers) && Equals(AllStats, other.AllStats) && PollDate.Equals(other.PollDate);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                var hashCode = Name?.GetHashCode() ?? 0;
                hashCode = (hashCode * 397) ^ (GroupLinkName?.GetHashCode() ?? 0);
                hashCode = (hashCode * 397) ^ (GroupName?.GetHashCode() ?? 0);
                hashCode = (hashCode * 397) ^ (ProxyName?.GetHashCode() ?? 0);
                hashCode = (hashCode * 397) ^ (NiceName?.GetHashCode() ?? 0);
                hashCode = (hashCode * 397) ^ HasAdminLogin.GetHashCode();
                hashCode = (hashCode * 397) ^ (int)GroupStatus;
                hashCode = (hashCode * 397) ^ (AllServers?.GetHashCode() ?? 0);
                hashCode = (hashCode * 397) ^ (AllStats?.GetHashCode() ?? 0);
                hashCode = (hashCode * 397) ^ PollDate.GetHashCode();
                return hashCode;
            }
        }
    }

    public class ServerGroupKey : IEquatable<ServerGroupKey>
    {
        public string Description { get; }
        public StatusType Type { get; }
        public bool IsFrontend { get; }
        public bool IsChecked { get; }
        public bool IsServer { get; }

        public ServerGroupKey(string description, StatusType type, bool isFrontEnd, bool isChecked, bool isServer)
        {
            Description = description;
            Type = type;
            IsFrontend = isFrontEnd;
            IsChecked = isChecked;
            IsServer = isServer;
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
            {
                return false;
            }
            if (ReferenceEquals(this, obj))
            {
                return true;
            }
            if (obj.GetType() != this.GetType())
            {
                return false;
            }
            return Equals((ServerGroupKey)obj);
        }
        public bool Equals(ServerGroupKey other)
        {
            if (ReferenceEquals(null, other))
            {
                return false;
            }
            if (ReferenceEquals(this, other))
            {
                return true;
            }
            return string.Equals(Description, other.Description) && Type == other.Type && IsFrontend == other.IsFrontend && IsChecked == other.IsChecked && IsServer == other.IsServer;
        }

        public override int GetHashCode()
        {
            unchecked
            {
                var hashCode = Description?.GetHashCode() ?? 0;
                hashCode = (hashCode * 397) ^ (int)Type;
                hashCode = (hashCode * 397) ^ IsFrontend.GetHashCode();
                hashCode = (hashCode * 397) ^ IsChecked.GetHashCode();
                hashCode = (hashCode * 397) ^ IsServer.GetHashCode();
                return hashCode;
            }
        }
    }

    public static ConcurrentDictionary<Action, string> ActionLists = new ConcurrentDictionary<Action, string>();

    private string GetActionString(Action action)
    {
        string result;
        if (!ActionLists.TryGetValue(action, out result))
        {
            var strings = new List<string>();
            foreach (Action a in Enum.GetValues(typeof(Action)))
            {
                if ((action & a) != 0)
                {
                    strings.Add(a.ToString());
                }
            }
            result = strings.ToJson().ToString();
            ActionLists.TryAdd(action, result);
        }
        return result;
    }

    public string GetActions(ProxyGroupKey group)
    {
        var enabled = Action.None;
        if (group.AllStats.Any(s => s.OutOfRotation))
        {
            enabled |= Action.Ready;
        }
        if (group.AllStats.Any(s => !s.InDrain))
        {
            enabled |= Action.Drain;
        }
        if (group.AllStats.Any(s => !s.InMaintenance))
        {
            enabled |= Action.Maintenance;
        }

        if (group.AllServers.Any(s => s.IsChecked))
        {
            enabled |= Action.HealthCheckDisable | Action.HealthForceUp | Action.HealthForceNoLB | Action.HealthForceDown;
        }
        else
        {
            enabled |= Action.HealthCheckEnable;
        }
        enabled |= Action.KillSessions; // TODO: Always enabled?

        return GetActionString(enabled);
    }

    public string GetActions(IGrouping<ServerGroupKey, Item> group)
    {
        var enabled = Action.None;
        if (group.Any(s => s.OutOfRotation))
        {
            enabled |= Action.Ready;
        }
        if (group.Any(s => !s.InDrain))
        {
            enabled |= Action.Drain;
        }
        if (group.Any(s => !s.InMaintenance))
        {
            enabled |= Action.Maintenance;
        }

        if (group.Key.IsChecked)
        {
            enabled |= Action.HealthCheckDisable | Action.HealthForceUp | Action.HealthForceNoLB | Action.HealthForceDown;
        }
        else
        {
            enabled |= Action.HealthCheckEnable;
        }
        enabled |= Action.KillSessions; // TODO: Always enabled?

        return GetActionString(enabled);
    }
}
<div class="row js-refresh" data-name="haproxy">
    <div class="col-md-@(anyAdmin ? "9" : "12")">
        @if (Model.SelectedGroup != null && erroring.Any())
        {
            <div class="alert alert-warning">
                <h5>There was an error querying HAProxy for status.</h5>
                @foreach (var i in erroring)
                {
                    <div>
                        <strong>@i.Group.Name: @i.Name</strong> (<a href="@i.Url">@i.Url</a>)
                        <p>@i.Proxies.ErrorMessage</p>
                    </div>
                }
            </div>
        }
        @if (groups.Any())
        {
            <table class="table table-striped table-responsive table-super-condensed table-actions actions-first table-hover">
                @foreach (var g in groups)
                {
                    var showAdmin = isAdmin && g.HasAdminLogin;
                    var combinedStats = g.AllStats.GroupBy(p => new ServerGroupKey(p.Description, p.Type, p.IsFrontend, p.IsChecked, p.IsServer)).ToList();
                    var anyUp = g.AllServers.Any(s => !s.OutOfRotation);
                    var anyDown = g.AllServers.Any(s => s.OutOfRotation);

                    <tbody class="@(g.GroupLinkName == Model.WatchProxy ? "watched-proxy" : "")" data-group="@g.GroupName" data-proxy="@g.ProxyName">
                        <tr class="category-row" title="Name: @g.GroupName">
                            <th colspan="12">
                                <h5>
                                    @if (showAdmin)
                                    {
                                        <a href="#" data-action="@Action.Ready"
                                           class="js-haproxy-action fa fa-plus-circle text-success hover-pulsate@(anyDown ? null : " disabled")"
                                           title="put all servers server into rotation for this backend"></a>
                                        <a href="#" data-action="@Action.Drain"
                                           class="js-haproxy-action fa fa-minus-circle text-danger hover-pulsate@(anyUp ? null : " disabled")"
                                           title="take all servers out of rotation for this backend"></a>
                                        <div class="btn-group dropdown js-dropdown-actions" data-actions="@GetActions(g)">
                                            <a href="#" class="hover-spin" title="Actions for all servers in this group">
                                                <span class="fa fa-cog"></span><span class="caret"></span>
                                            </a>
                                        </div>
                                    }
                                    <a href="@Url.Action(nameof(HAProxyController.Dashboard), new { watch = g.GroupLinkName })@groupQS">@g.GroupStatus.IconSpan() @g.GroupName: @g.NiceName</a>
                                    <span class="pull-right text-muted small">As of: @g.PollDate.ToRelativeTimeSpanMini()</span>
                                </h5>
                            </th>
                        </tr>
                        <tr>
                            <th></th>
                            <th>Server</th>
                            <th>Instances</th>
                            <th>Sessions</th>
                            <th colspan="2">Rate</th>
                            <th colspan="2">Bandwidth</th>
                            <th colspan="2">Status</th>
                        </tr>
                        @foreach (var s in combinedStats.OrderBy(sg => sg.Key.Type == StatusType.Frontend || sg.Key.Type == StatusType.Backend).ThenBy(sg => sg.Key.Description))
                        {
                            var stat = s.Key;
                            var proxyStatus = s.GetWorstStatus();
                            var latestStat = s.OrderBy(si => si.LastStatusChange).Last();
                            // If everything is green, render it as a backup server if appropriate
                            var isBackup = s.Any(sp => sp.IsServer && sp.Backup > 0);
                            var rowClass = proxyStatus == MonitorStatus.Good
                                ? isBackup ? "info" : ""
                                : proxyStatus.RowClass();

                            <tr class="@(GetRowClass(stat.Type)) status @rowClass" data-server="@stat.Description">
                                <td>
                                    @if (stat.IsServer && showAdmin)
                                    {
                                        <div class="btn-group dropdown js-dropdown-actions" data-actions="@GetActions(s)">
                                            <a href="#" class="hover-spin" title="Actions for this server">
                                                @Icon.Cog<span class="caret"></span>
                                            </a>
                                        </div>
                                    }
                                </td>
                                <td>@proxyStatus.IconSpan() @stat.Description@if (isBackup)
                                { <span class="text-primary"> (Backup)</span>}</td>
                                <td class="load-balancers">
                                    @foreach (var ip in s)
                                    {
                                        var p = statMapping[ip];
                                        // TODO: Primary/active detection
                                        var isPrimary = false;
                                        <span title="@ip.CheckStatusString" class="@(isPrimary ? "primary" : null)@(ip.IsServer && ip.Backup > 0 ? " text-muted" : null)">@ip.IconSpan() @p.Instance.Name</span>
                                    }
                                </td>
                                <td title="@CombinedStat("Current sessions", s, si => si.CurrentSessions.ToComma())">
                                    <span class="text-muted">Cur:</span>
                                    @s.Sum(si => si.CurrentSessions).ToComma()
                                </td>
                                <td title="@CombinedStat("New Sessions per second (current)", s, si => si.CurrentNewSessionsPerSecond.ToComma())">
                                    <span class="text-muted">Cur:</span>
                                    @s.Sum(si => si.CurrentNewSessionsPerSecond).ToComma()
                                </td>
                                <td title="@CombinedStat("Max Sessions per second", s, si => si.MaxNewSessionPerSecond.ToComma())">
                                    <span class="text-muted">Max:</span>
                                    @s.Sum(si => si.MaxNewSessionPerSecond).ToComma()
                                </td>
                                <td title="@CombinedStat("Bytes transferred in", s, si => $"{si.BytesIn.ToComma()} bytes ({si.BytesIn.ToHumanReadableSize()})")">
                                    <span class="text-muted">In:</span>
                                    @s.Sum(si => si.BytesIn).ToHumanReadableSize()
                                </td>
                                <td title="@CombinedStat("Bytes transferred out", s, si => $"{si.BytesOut.ToComma()} bytes ({si.BytesOut.ToHumanReadableSize()})")">
                                    <span class="text-muted">Out:</span>
                                    @s.Sum(si => si.BytesOut).ToHumanReadableSize()
                                </td>
                                <td title="@CombinedStat("Status", s, si => (!si.IsFrontend && si.IsChecked ? si.LastStatusChange.ToRelativeTimeMiniAll() : "") + " (" + si.Status + ")")" class="@(stat.IsChecked ? null : "text-muted")">
                                    @(!stat.IsFrontend && stat.IsChecked ? s.Max(si => si.LastStatusChange).ToRelativeTimeMiniAll() : "") @latestStat.ToStatusSpan()
                                </td>
                                <td title="@CombinedStat("Poll Status", s, si => $"{si.CheckStatusString} - {si.CheckCode.ToString()} ({si.CheckDurationMiliseconds.ToString()}ms)") ">
                                    @latestStat.CheckStatusString@(latestStat.CheckCode > 0 && latestStat.CheckCode != 200 ? " (" + latestStat.CheckCode.ToString() + ")" : "")
                                </td>
                            </tr>
                        }
                    </tbody>
                }
            </table>
        }
        else if (!erroring.Any() && instances.All(i => i.HasPolledCacheSuccessfully))
        {
            <div class="no-content">No HAProxy proxies are Available</div>
        }
    </div>
    @if (anyAdmin)
    {
        var adminGroups = proxies.GroupBy(p => p.Instance.Group.Name).Select(g => new
        {
            Name = g.Key,
            Servers = g.SelectMany(p => p.Servers)
        });
        var servers = proxies.SelectMany(p => p.Servers).GroupBy(s => s.ServerName).Select(g => new
        {
            Server = g.Key,
            Up = g.Count(s => s.MonitorStatus == MonitorStatus.Good),
            Out = g.Count(s => s.OutOfRotation),
            Down = g.Count(s => s.MonitorStatus != MonitorStatus.Good && !s.InMaintenance),
            MonitorStatus = g.GetWorstStatus()
        }).OrderBy(s => s.Server).ToList();
        <div class="col-md-3">
            <div class="haproxy-admin-servers">
                <table class="table table-striped table-responsive table-super-condensed table-actions table-hover">
                    <tbody>
                        <tr class="category-row">
                            <th colspan="5">
                                <h5>
                                    Instances
                                    <span class="pull-right small"> @Poll.Now(instances)</span>
                                </h5>
                            </th>
                        </tr>
                        <tr>
                            <th></th>
                            <th>Instance</th>
                            <th>In</th>
                            <th>Out</th>
                            <th>Down</th>
                        </tr>
                    </tbody>
                    <tbody>
                        @foreach (var g in adminGroups)
                        {
                            var gIn = g.Servers.Count(s => s.MonitorStatus == MonitorStatus.Good);
                            var gOut = g.Servers.Count(s => s.OutOfRotation);
                            var gDown = g.Servers.Count(s => s.MonitorStatus != MonitorStatus.Good && !s.OutOfRotation);
                            var status = g.Servers.GetWorstStatus();

                            <tr class="status @status.RowClass()">
                                <td>
                                    <a href="#" data-group="@g.Name" data-action="@Action.Ready"
                                       class="js-haproxy-action fa fa-plus-circle text-success hover-pulsate@(gOut > 0 ? null : " disabled")"
                                       title="put all proxies in this group into rotation"></a>
                                    <a href="#" data-group="@g.Name" data-action="@Action.Drain"
                                       class="js-haproxy-action fa fa-minus-circle text-danger hover-pulsate@(gIn + gDown > 0 ? null : " disabled")"
                                       title="take all proxies in this group out of rotation"></a>
                                </td>
                                <td>@status.IconSpan() @g.Name</td>
                                <td>@gIn.ToComma()</td>
                                <td>@gOut.ToComma()</td>
                                <td>@gDown.ToComma()</td>
                            </tr>
                        }
                    </tbody>
                    <tbody>
                        <tr class="category-row">
                            <th colspan="5">
                                <h5>Servers</h5>
                            </th>
                        </tr>
                        <tr>
                            <th></th>
                            <th>Server</th>
                            <th>In</th>
                            <th>Out</th>
                            <th>Down</th>
                        </tr>
                    </tbody>
                    <tbody>
                        @foreach (var s in servers)
                        {
                            var status = s.Down > 0 && s.Up == 0 ? MonitorStatus.Critical : s.Down > 0 && s.Up > 0 ? MonitorStatus.Warning : MonitorStatus.Good;
                            <tr class="status @s.MonitorStatus.RowClass()">
                                <td>
                                    <a href="#" data-server="@s.Server" data-action="@Action.Ready"
                                       class="js-haproxy-action fa fa-plus-circle text-success hover-pulsate@(s.Out > 0 ? null : " disabled")"
                                       title="put this server into rotation everywhere"></a>
                                    <a href="#" data-server="@s.Server" data-action="@Action.Drain"
                                       class="js-haproxy-action fa fa-minus-circle text-danger hover-pulsate@(s.Up + s.Down > 0 ? null : " disabled")"
                                       title="take this server out of rotation everywhere"></a>
                                </td>
                                <td>@status.IconSpan() @s.Server</td>
                                <td>@s.Up.ToComma()</td>
                                <td>@s.Out.ToComma()</td>
                                <td>@s.Down.ToComma()</td>
                            </tr>
                        }
                    </tbody>
                </table>
            </div>
        </div>
        <div class="js-haproxy-server-dropdown">
            <ul class="dropdown-menu js-haproxy-actions">
                <li><a href="#" data-action="@Action.Ready"><i class="fa fa-plus-circle fa-fw text-success" aria-hidden="true"></i> Enable Server (READY)</a></li>
                <li><a href="#" data-action="@Action.Drain"><i class="fa fa-minus-circle fa-fw text-primary" aria-hidden="true"></i> Disable Server (DRAIN)</a></li>
                <li><a href="#" data-action="@Action.Maintenance"><i class="fa fa-wrench fa-fw text-warning" aria-hidden="true"></i> Disable Server (MAINT)</a></li>
                <li class="divider"></li>
                <li><a href="#" data-action="@Action.HealthCheckEnable"><i class="fa fa-heartbeat fa-fw text-success" aria-hidden="true"></i> Health: Enable Checks</a></li>
                <li><a href="#" data-action="@Action.HealthCheckDisable"><i class="fa fa-heartbeat fa-fw text-danger" aria-hidden="true"></i> Health: Disable Checks</a></li>
                <li class="divider"></li>
                <li><a href="#" data-action="@Action.HealthForceUp"><i class="fa fa-caret-square-o-up fa-fw text-success" aria-hidden="true"></i> Health: Force UP</a></li>
                <li><a href="#" data-action="@Action.HealthForceNoLB"><i class="fa fa-square-o fa-fw text-warning" aria-hidden="true"></i> Health: Force NOLB</a></li>
                <li><a href="#" data-action="@Action.HealthForceDown"><i class="fa fa-caret-square-o-down fa-fw text-danger" aria-hidden="true"></i> Health: Force DOWN</a></li>
                <li class="divider"></li>
                <li><a href="#" data-action="@Action.KillSessions"><i class="fa fa-bomb fa-fw text-danger" aria-hidden="true"></i> Kill all sessions</a></li>
            </ul>
        </div>
    }
</div>